const std = @import("std");
const typeUtil = @import("typeUtil.zig");
const TerCol = @import("tercol.zig");

pub const u = typeUtil.u;
pub const i = typeUtil.i;

pub const BitAdr: comptime_int = 4;
pub const BitLen: comptime_int = 1 << BitAdr;

pub const ONE: u(BitLen) = 1;

ext: u(BitLen),
pol: u(BitLen),

const Self = @This();

pub fn init(ext: u(BitLen), pol: u(BitLen)) Self {
    return Self{
        .ext = ext,
        .pol = pol,
    };
}

pub fn zero() Self {
    return Self{
        .ext = 0,
        .pol = 0,
    };
}

pub fn set(self: *Self, pos: i(BitAdr)) !void {
    const pos_u = @as(u(BitAdr), @intCast(@abs(pos)));
    self.ext |= (ONE << pos_u);
    if (pos >= 0) {
        self.pol |= (ONE << pos_u);
    } else {
        self.pol &= ~(ONE << pos_u);
    }
}

pub fn rst(self: *Self, pos: i(BitAdr)) !void {
    const pos_u = @as(u(BitAdr), @intCast(@abs(pos)));
    self.ext &= ~(ONE << pos_u);
    self.pol &= ~(ONE << pos_u);
}

pub fn clr(self: *Self) void {
    self.ext = 0;
    self.pol = 0;
}

pub fn comm(self: Self, other: Self) Self {
    const commExt = self.ext & other.ext;
    const difPol = self.pol ^ other.pol;
    const disCond = commExt & difPol;
    if (disCond != 0) {
        return Self{ .ext = 0, .pol = 0 };
    }
    const resultExt = self.ext | other.ext;
    const resultPol = resultExt & (self.pol | other.pol);
    return Self{ .ext = resultExt, .pol = resultPol };
}

pub fn emptyQ(self: Self) bool {
    return self.ext == 0;
}

pub fn equalQ(self: Self, other: Self) bool {
    return self.ext == other.ext and self.pol == other.pol;
}

pub fn print(self: Self, postfix: ?[]const u8) void {
    for (0..BitLen) |dex| {
        const posBit: u(BitAdr) = @intCast(dex);
        const ext = (self.ext & (ONE << posBit)) != 0;
        const pol = (self.pol & (ONE << posBit)) != 0;
        const color = if (!ext) "90" else if (pol) "32" else "31";
        std.debug.print("\x1b[{s}m▀\x1b[0m", .{color});
    }
    if (postfix) |p| {
        std.debug.print("{s}", .{p});
    }
}

pub fn println(self: Self, postfix: []const u8) void {
    for (0..BitLen) |dex| {
        const posBit: u(BitAdr) = @intCast(dex);
        const ext = (self.ext & (ONE << posBit)) != 0;
        const pol = (self.pol & (ONE << posBit)) != 0;
        const color = if (!ext) "90" else if (pol) "32" else "31";
        std.debug.print("\x1b[{s}m▀\x1b[0m", .{color});
    }
    std.debug.print("{s}", .{postfix});
}

pub fn diff(self: Self, other: Self, allocator: std.mem.Allocator) !TerCol {
    var result = TerCol.init(allocator, BitLen);
    const intersection = self.comm(other);

    if (intersection.emptyQ()) {
        try result.add(self);
        return result;
    }

    if (self.equalQ(intersection)) {
        return result;
    }

    const unsetPos = ~self.ext & other.ext;
    const difExt = self.ext;
    const difPol = self.ext & self.pol;

    if (unsetPos == 0) {
        try result.add(Self.init(difExt, difPol));
        return result;
    }

    // For each unset position in self that is set in other,
    // create a TerSeg with that position set to the opposite value of other
    var remPos = unsetPos;
    std.debug.print("unsetPos: {b:0>8}\n", .{unsetPos});
    while (remPos != 0) {
        const pos: u(BitAdr) = @intCast(@ctz(remPos));
        const posMask = ONE << pos;
        // Set this position as fixed, with polarity opposite to other
        const newExt = (difExt & ~unsetPos) | remPos;
        const newPol = difPol | (other.pol ^ posMask);
        try result.add(Self.init(newExt, newPol));
        remPos ^= posMask;
    }

    return result;
}

test "TerSeg - initialization" {
    const seg = init(0, 0);
    try std.testing.expect(seg.ext == 0);
    try std.testing.expect(seg.pol == 0);
    try std.testing.expect(seg.emptyQ());

    const zeroSeg = zero();
    try std.testing.expect(zeroSeg.ext == 0);
    try std.testing.expect(zeroSeg.pol == 0);
}

test "TerSeg - set and rst" {
    var seg = zero();

    // Test positive position
    try seg.set(2);
    try std.testing.expect(seg.ext == (ONE << 2));
    try std.testing.expect(seg.pol == (ONE << 2));

    // Test negative position
    try seg.set(-2);
    try std.testing.expect(seg.ext == (ONE << 2));
    try std.testing.expect(seg.pol == 0);

    // Test rst
    try seg.rst(2);
    try std.testing.expect(seg.ext == 0);
    try std.testing.expect(seg.pol == 0);
}

test "TerSeg - comm (intersection)" {
    var seg1 = zero();
    var seg2 = zero();

    // Test compatible segments
    try seg1.set(1);
    try seg2.set(1);
    const result1 = seg1.comm(seg2);
    try std.testing.expect(result1.ext == (ONE << 1));
    try std.testing.expect(result1.pol == (ONE << 1));

    // Test incompatible segments (different polarities)
    try seg1.set(2);
    try seg2.set(-2);
    const result2 = seg1.comm(seg2);
    try std.testing.expect(result2.emptyQ());
}

test "TerSeg - equality" {
    var seg1 = zero();
    var seg2 = zero();

    try std.testing.expect(seg1.equalQ(seg2));

    try seg1.set(1);
    try std.testing.expect(!seg1.equalQ(seg2));

    try seg2.set(1);
    try std.testing.expect(seg1.equalQ(seg2));
}

test "TerSeg - empty check" {
    var seg = zero();
    try std.testing.expect(seg.emptyQ());

    try seg.set(1);
    try std.testing.expect(!seg.emptyQ());

    try seg.rst(1);
    try std.testing.expect(seg.emptyQ());
}

fn printTestCase(
    seg1: Self,
    seg2: Self,
    result: TerCol,
    label: []const u8,
) void {
    std.debug.print("\x1b[1;33m{s}\x1b[0m\n", .{label}); // Bold yellow
    std.debug.print("A: ", .{});
    seg1.print("\n");
    std.debug.print("B: ", .{});
    seg2.print("\n");
    std.debug.print("R: ", .{});
    result.print();
    std.debug.print("\n", .{});
}

test "TerSeg - diff" {
    const allocator = std.testing.allocator;
    var seg1 = zero();
    var seg2 = zero();

    // Test empty intersection
    try seg1.set(1);
    try seg2.set(-1);
    var result1 = try seg1.diff(seg2, allocator);
    printTestCase(seg1, seg2, result1, "no intersection");
    defer result1.deinit();
    // try std.testing.expect(result1.len() == 1);
    // try std.testing.expect(result1.seg.items[0].equalQ(seg1));

    // Test complete intersection
    seg1.clr();
    seg2.clr();
    try seg1.set(2);
    try seg2.set(2);
    var result2 = try seg1.diff(seg2, allocator);
    printTestCase(seg1, seg2, result2, "complete intersection");
    defer result2.deinit();
    // try std.testing.expect(result2.len() == 0);

    // Test partial intersection with don't-care positions
    seg1.clr();
    seg2.clr();
    try seg1.set(3);
    try seg2.set(-3);
    var result3 = try seg1.diff(seg2, allocator);
    printTestCase(seg1, seg2, result3, "partial intersection");
    defer result3.deinit();
    // try std.testing.expect(result3.len() > 0);

    // Test multiple don't-care positions
    seg1.clr();
    seg2.clr();
    try seg1.set(0);
    try seg1.set(3);
    try seg2.set(0);
    try seg2.set(-1);
    try seg2.set(-2);
    try seg2.set(3);

    var result4 = try seg1.diff(seg2, allocator);
    printTestCase(seg1, seg2, result4, "multiple don't-care positions");
    defer result4.deinit();
    // try std.testing.expect(result4.len() > 1);
}
